package com.gryffonx.DiceRoller;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
 * Series of static methods the simulate the rolling of various
 * dice during a character generation or during gameplay.  Each method
 * simulates a different type of dice, differentiated only by the number
 * of sides on the dice.  Each takes an argument of count, which tells
 * how many of the dice are to be rolled.<br>
 * <p/>
 * All methods use the Math.random method, while setting a Math.floor equal
 * to the number of sides for the "dice".  This ensures that the number
 * generated never goes over the number of sides being represented.  If the
 * user specifies more than one roll is to take place, the rolls are summed before
 * being returned by the method.  If the action that calls the method passes either
 * a 0 or a null for the number of rolls, the method will default to 1.
 * <p/>
 * Computers are deterministic -- they do the same thing whenever they have the same input.
 * Therefore, in some way it is impossible for a computer to compute a truly random number.
 * But the methods that are used generate sequences of numbers that meet the statistical
 * requirements of randomness. Because the numbers are not truly random, they are sometimes
 * referred to as <b>pseudorandom numbers</b>. These random number generates will produce exactly the
 * same sequence of pseudorandom numbers if they start with the same initial value.
 * This initial value is called the <b>seed</b>.
 * <p/>
 * If we start a program with the same random seed, we will get the same sequence of numbers.
 * The default seed is the current time in milliseconds since 1970.
 * This works very well, but you might want to set the seed so that the sequence can be reproduced
 * when you are testing your program.
 *
 * @author Erik Stell
 * @version 1.0
 */
public class StaticDiceRoller {


    /**
     * Static method that simulates an odd sided dice roll.  The count attribute
     * is to specify the number of dice to roll
     *
     * @param sides - the number of sides for the dice
     * @param count - the number of dice to be rolled.
     * @return int value of the returned "roll", from 1 - the number specified
     */
    public static int rolldSomething(int sides, int count) {
        int retVal = 0;
        if (count <= 0) {
            count = 1;
        }
        try {
            for (int i = 0; i < count; i++) {
                int tempVal = pickNumber(sides);
                Thread.sleep(5);
                //with each pass through the loop, add the values to the retVal variable.
                retVal = retVal + tempVal;
            }
            if (retVal > (sides * count)) {
                retVal = (sides * count);
            }
            if (retVal <= 0) {
                retVal = 1;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return retVal;
    }

    /**
     * Static method that simulates an odd sided dice roll.  The count attribute
     * is to specify the number of dice to roll
     *
     * @param s     - the number of sides for the dice
     * @param c     - the number of dice to be rolled.
     * @param old - specifies the older or new method to use for generation
     * @return int value of the returned "roll", from 1 - the number specified
     * @throws Exception - if an error is generated
     */
    public static int rolldSomething(int s, int c, boolean old) throws Exception {
        int r = 1;
        if (c <= 0) {
            c = 1;
        }
        if (old) {
            for (int i = 0; i < c; i++) {
                //perform the random generation for the "roll", and assign it to a temp variable
                Double t = Math.floor(Math.random() * s);

                //sleep the thread to prevent a possible dupe.
                Thread.sleep(5);

                //with each pass through the loop, add the values to the retVal variable.
                r = r + t.intValue();
            }
            if (r > (s * c)) {
                r = (s * c);
            }
        } else {
            r = rolldSomething(s, c);
        }
        return r;
    }

    /**
     * Static method that simulates an odd sided dice roll.  The count attribute
     * is to specify the number of dice to roll
     *
     * @param sides - the number of sides for the dice
     * @param count - the number of dice to be rolled.
     * @return int value of the returned "roll", from 1 - the number specified
     */
    public static int rolldStat(int sides, int count) {
        int retVal = 1;
        if (count <= 0) {
            count = 1;
        }
        try {
            if (count == 4) {
                int[] mine = new int[4];
                mine[0] = rolldSomething(6, 1, true);
                mine[1] = rolldSomething(6, 1, false);
                mine[2] = rolldSomething(6, 1, true);
                mine[3] = rolldSomething(6, 1, false);
                Arrays.sort(mine);
                retVal = mine[1] + mine[2] + mine[3];
                Thread.sleep(15);
            } else {
                for (int i = 0; i < count; i++) {
                    //perform the random generation for the "roll"
                    Double tempVal = Math.floor(Math.random() * sides);
                    //with each pass through the loop, add the values to the retVal variable.
                    retVal = retVal + tempVal.intValue();
                }
                if (retVal > (sides * count)) {
                    retVal = (sides * count);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return retVal;
    }

    /**
     * Get a single random integer in the range 0 (inclusive) to N (exclusive).
     * <p/>
     * Important: if this method is called multiple times in rapid
     * succession, it will return the SAME result.
     *
     * @param hn - high number limit; must be greater than 0.
     * @return randomly generated number
     * @throws IllegalArgumentException if param does not comply.
     */
    private static int pickNumber(int hn) {
        if (hn <= 0) {
            throw new IllegalArgumentException("UpperLimit must be positive: " + hn);
        }
        //if the no-argument constructor is used, the seed is taken as
        //the current time; thus, Random objects created in rapid succession
        //in this way will have the same seed, and will generate the same sequence
        //of random numbers.
        Random g = new Random();
        return g.nextInt(hn);
    }

    /**
     * Generate a single random integer between aLowerLimit
     * and aUpperLimit, inclusive.
     * <p/>
     * Important: if this method is called twice in rapid succession, it will
     * return the same value.
     *
     * @param ln - low number in range
     * @param hn - high number in range
     * @return - generated value
     * @throws IllegalArgumentException if aLowerLimit is not less
     *                                  than aUpperLimit.
     */
    public static int pickNumberInRange(int ln, int hn) {
        if (ln >= hn) {
            StringBuffer message = new StringBuffer();
            message.append("Lower limit (");
            message.append(ln);
            message.append(") must be lower than Upper limit (");
            message.append(hn);
            message.append(")");
            throw new IllegalArgumentException(message.toString());
        }

        Random g = new Random();
        // get the range, casting to long to avoid overflow problems
        long range = (long) hn - (long) ln + 1;
        // compute a fraction of the range, 0 <= frac < range
        long fraction = (long) (range * g.nextDouble());
        return (int) (fraction + ln);
    }

    /**
     * Get a List of random Integers in the range 0 (inclusive) to N (exclusive).
     *
     * @param aUpperLimit must be greater than 0.
     * @param aListSize   must be greater than 0.
     * @param allowDupes - if list can contain duplicates or not...
     * @return - List of randomly generated numbers
     * @throws IllegalArgumentException if param does not comply.
     * @throws InterruptedException - occurs during the sleep proccess.
     */
    public List<Integer> pickNumbers(int aUpperLimit, int aListSize, boolean allowDupes) throws InterruptedException {
        if (aUpperLimit <= 0) {
            throw new IllegalArgumentException("UpperLimit must be positive: " + aUpperLimit);
        }
        if (aListSize <= 0) {
            throw new IllegalArgumentException("Size of returned List must be greater than 0.");
        }
        List<Integer> rslt = new ArrayList<Integer>();
        for (int idx = 0; idx < aListSize; ++idx) {
            Random g = new Random();
            int gr = g.nextInt(aUpperLimit);
            if (allowDupes) {
                rslt.add(gr);
            } else {
                if (!rslt.contains(gr)) {
                    rslt.add(gr);
                } else {
                    idx--;
                }
            }
            Thread.sleep(500);
        }
        return rslt;
    }
}
